package com.hexacta.hrs.services.staffing.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateUtils;
import org.vectrics.common.util.DateUtil;
import org.vectrics.employee.Employee;
import org.vectrics.location.Location;

import com.hexacta.hrs.domain.helper.EmployeeAssignmentHelper;
import com.hexacta.hrs.domain.staffing.assignment.EmployeeAssignment;
import com.hexacta.hrs.repositories.EmployeeAssignmentRepository;
import com.hexacta.hrs.services.employee.EmployeeService;
import com.hexacta.hrs.services.staffing.EmployeeAssignmentService;
import com.hexacta.hrs.services.timeoff.TimeOffService;
import com.hexacta.hrs.timeoff.Status;
import com.hexacta.hrs.timeoff.TimeOff;

/**
 * El servicio de {@link EmployeeAssignment}.
 * 
 * @author ncortinez
 */
public class EmployeeAssignmentServiceImpl implements EmployeeAssignmentService {
    private EmployeeAssignmentRepository employeeAssignmentRepository;

    private EmployeeService employeeService;

    private TimeOffService timeOffService;

    private static List<Status> statusToShow = new ArrayList<Status>();

    /**
     * 
     */
    public EmployeeAssignmentServiceImpl() {
        statusToShow.add(Status.APPROVED);
        statusToShow.add(Status.CONFIRMED);
        statusToShow.add(Status.REQUESTED);
    }

    public EmployeeService getEmployeeService() {
        return employeeService;
    }

    public void setEmployeeService(final EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    public EmployeeAssignmentRepository getEmployeeAssignmentRepository() {
        return employeeAssignmentRepository;
    }

    public void setEmployeeAssignmentRepository(final EmployeeAssignmentRepository employeeAssignmentRepository) {
        this.employeeAssignmentRepository = employeeAssignmentRepository;
    }

    @Override
    public List<EmployeeAssignment> findAll() {
        return getEmployeeAssignmentRepository().findAll();
    }

    @Override
    public List<EmployeeAssignment> findAllIncludingNoAssignableToProjectEmployees() {
        return getEmployeeAssignmentRepository().findAllIncludingNoAssignableToProjectEmployees();
    }

    @Override
    public EmployeeAssignment createNew() {
        return new EmployeeAssignment();
    }

    @Override
    public void delete(final EmployeeAssignment recordObject) {
        getEmployeeAssignmentRepository().delete(recordObject);
    }

    @Override
    public void save(final EmployeeAssignment recordObject) {
        getEmployeeAssignmentRepository().save(recordObject);
    }

    @Override
    public Collection<EmployeeAssignment> searchByCriteria(final EmployeeAssignment criteria) {
        return getEmployeeAssignmentRepository().matching(criteria);
    }

    @Override
    public EmployeeAssignment searchById(final long id) {
        return getEmployeeAssignmentRepository().forId(id);
    }
    
    @Override
	public Calendar availabilityInMonths(Employee employee) {
		return getEmployeeAssignmentRepository().availabilityInMonths(employee);
	}

	/** @{inheritDoc */
    @Override
    public List<EmployeeAssignmentDTO> findAvailableResource(final Employee selectedEmployee, final Date from,
            final Date to, final boolean showTentative, final boolean noAsignado, final boolean asignadoCliente,
            final boolean addonAvailable, final boolean addonNotAvailable, final boolean interno,
            final boolean timeOff, final String[] roles, final String proyectId,
            final boolean onlyElegibleAsProjectMember, Location location) {

        List<EmployeeAssignmentDTO> toReturn = new ArrayList<EmployeeAssignmentDTO>();
        List<Employee> employeesToFilter = null;

        // Busco estrictamente los DTOS que matchean los criterios
        if (asignadoCliente || addonAvailable || addonNotAvailable || interno || !(noAsignado || timeOff)) {
            toReturn.addAll(employeeAssignmentRepository.getMatchingAssigmentDTOsBetweenDates(selectedEmployee,
                    showTentative, asignadoCliente, addonAvailable, addonNotAvailable, interno, roles, proyectId, from,
                    to, onlyElegibleAsProjectMember, location));
        }
        // Creo los DTOs especiales si pide no asignados o timeoff

        if (noAsignado || timeOff) {
            employeesToFilter = loadEmployeesWithRoles(selectedEmployee, from, to, onlyElegibleAsProjectMember, roles);
            Map<Long, List<SimpleEmployeeAssignmentDTO>> dtoMap = new HashMap<Long, List<SimpleEmployeeAssignmentDTO>>();

            if (noAsignado) {
                dtoMap.putAll(buildSimpleDTOMap(selectedEmployee, from, to, roles, onlyElegibleAsProjectMember));
            }

            for (Employee employee : employeesToFilter) {

                if (noAsignado) {
                    if (!dtoMap.containsKey(employee.getId())) {
                        toReturn.addAll(EmployeeAssignmentHelper.createDTO(employee, from, to));
                    } else {
                        toReturn.addAll(createPartialAssignments(from, to, employee, dtoMap.get(employee.getId())));
                    }
                }

                if (timeOff) {
                    toReturn.addAll(findTimeOff(employee, from, to));
                }

            }
        }

        Collections.sort(toReturn, new EmployeeAssignmentDTOComparatorName());

        return toReturn;
    }

    /**
     * @param selectedEmployee
     * @param from
     * @param to
     * @param roles
     * @param onlyElegibleAsProjectMember
     */
    private Map<Long, List<SimpleEmployeeAssignmentDTO>> buildSimpleDTOMap(final Employee selectedEmployee,
            final Date from, final Date to, final String[] roles, final boolean onlyElegibleAsProjectMember) {
        Map<Long, List<SimpleEmployeeAssignmentDTO>> dtoMap = new HashMap<Long, List<SimpleEmployeeAssignmentDTO>>();
        Collection<SimpleEmployeeAssignmentDTO> simpleDTOs = employeeAssignmentRepository
                .getSimpleMatchingAssigmentDTOsBetweenDates(selectedEmployee, roles, from, to,
                        onlyElegibleAsProjectMember);

        for (SimpleEmployeeAssignmentDTO dto : simpleDTOs) {
            if (dtoMap.containsKey(dto.getEmployeeID())) {
                dtoMap.get(dto.getEmployeeID()).add(dto);
            } else {
                List<SimpleEmployeeAssignmentDTO> dtoList = new ArrayList<SimpleEmployeeAssignmentDTO>();
                dtoList.add(dto);
                dtoMap.put(dto.getEmployeeID(), dtoList);
            }
        }
        return dtoMap;
    }

    /**
     * @param selectedEmployee
     * @param from
     * @param to
     * @param onlyElegibleAsProjectMember
     * @param employeesToFilter
     */
    private List<Employee> loadEmployeesWithRoles(final Employee selectedEmployee, final Date from, final Date to,
            final boolean onlyElegibleAsProjectMember, final String[] roles) {

        List<Employee> employeesToFilter = new ArrayList<Employee>();
        if (selectedEmployee == null) {
            // Busco los empleados asignables (o no) en el período que tienen
            // los roles seleccionados
            employeesToFilter.addAll(employeeService.getEmployeesAssignableToProject(onlyElegibleAsProjectMember, from,
                    to, roles));
        } else {
            employeesToFilter.add(selectedEmployee);
        }
        return employeesToFilter;
    }

    private List<EmployeeAssignmentDTO> findTimeOff(final Employee employee, final Date from, final Date to) {

        List<EmployeeAssignmentDTO> toReturn = new ArrayList<EmployeeAssignmentDTO>();
        Collection<TimeOff> timeOffCol = getTimeOffService().findTimeOff(employee, from, to, statusToShow);
        for (TimeOff timeOff : timeOffCol) {
            toReturn.add(EmployeeAssignmentHelper.createDTO(timeOff));
        }
        return toReturn;
    }

    public List<EmployeeAssignment> findAssignmentsByEmployee(final Employee employee) {
        return employeeAssignmentRepository.findAssignmentsByEmployee(employee);
    }

    /**
     * Devuelve una lista con instancias de EmployeeAssignmentDTO para los
     * intervalos de tiempo donde el empleado tiene disponibilidad parcial o
     * total.
     * 
     * @param from
     * @param to
     * @param employee
     * @param assignments
     * @return
     */
    private List<EmployeeAssignmentDTO> createPartialAssignments(final Date from, final Date to,
            final Employee employee, final List<SimpleEmployeeAssignmentDTO> assignments) {
        // si no estaba empleado, corto las fechas
        Date realFrom = from;
        if (employee.getDateStart().after(from)) {
            realFrom = employee.getDateStart();
        }
        Date realTo = to;
        if (employee.getDateEnd() != null && employee.getDateEnd().before(to)) {
            realTo = employee.getDateEnd();
        }
        // la lista que vamos a devolver
        List<EmployeeAssignmentDTO> assignmentsToReturn = new ArrayList<EmployeeAssignmentDTO>();
        // voy recorriendo dia por dia, viendo si ese dia esta disponible.
        int diferencia = DateUtil.getDaysBetween(realFrom, realTo);
        for (int index = 0; index < diferencia; index++) {
            Date tempFrom = DateUtil.sumar(index, realFrom);

            if (!employee.isAssignable(tempFrom)) {
                continue;
            }

            double availabilitySum = calculateAvailabilitySum(assignments, tempFrom, tempFrom);
            int next = 1;
            // en caso de de que este disponible, me fijo los dias siguiente
            // para sabes hasta donde se debe crear el DTO del assignment
            if (availabilitySum < 100) {
                Date nextEqual = DateUtil.sumar(next, tempFrom);
                while (availabilitySum == calculateAvailabilitySum(assignments, nextEqual, nextEqual)
                        && DateUtil.menorIgual(nextEqual, realTo) && employee.isAssignable(nextEqual)) {
                    // Solo sigo si el porcentaje de disponibilidad es igual al
                    // del dia anterior y sigue siendo asignable.
                    next = next + 1;
                    nextEqual = DateUtil.sumar(next, tempFrom);
                }
                Date tempTo = DateUtil.sumar(next - 1, tempFrom);
                assignmentsToReturn.add(EmployeeAssignmentHelper.createDTO(employee, tempFrom, tempTo,
                        (100 - availabilitySum)));
            }
            // Debo restar 1 por que el for suma luego 1, y la variable next ya
            // posee la cantidad exacta de dias hasta que sea diferente. ademas
            // el valor inicial de next es 1
            index = index + next - 1;
        }
        return assignmentsToReturn;
    }

    /**
     * Caclula la disponibilidad de un empleado (no se pasa como parametro, se
     * asume que todos los assignments)
     * 
     */
    private double calculateAvailabilitySum(final List<SimpleEmployeeAssignmentDTO> simpleDTOs, final Date from,
            final Date to) {
        // Sumaremos los porcentajes de cada asignacion por la cantidad de dias
        // a dicha asignacion, para obtener el total. Luego, se compara con la
        // cantidad de dias requeridos * 100 (ya que este valor deberia ser el
        // que se obtiene cuando un empleado esta completamente asignado)
        double sumAssignments = 0;
        for (SimpleEmployeeAssignmentDTO dto : simpleDTOs) {
            if (DateUtils.isSameDay(from, dto.getFrom().getTime()) && DateUtils.isSameDay(to, dto.getTo().getTime())) {
                sumAssignments += DateUtil.getDaysBetween(from, to) * dto.getPorcentage();
            } else {
                Date calculateFrom, calculateTo;
                if (from.after(dto.getFrom().getTime())) {
                    calculateFrom = from;
                } else {
                    calculateFrom = dto.getFrom().getTime();
                }
                if (to.before(dto.getTo().getTime())) {
                    calculateTo = to;
                } else {
                    calculateTo = dto.getTo().getTime();
                }
                sumAssignments += DateUtil.getDaysBetween(calculateFrom, calculateTo) * dto.getPorcentage();
            }
        }
        return sumAssignments;
    }

    /**
     * @return the timeOffService
     */
    public TimeOffService getTimeOffService() {
        return timeOffService;
    }

    /**
     * @param timeOffService
     *            the timeOffService to set
     */
    public void setTimeOffService(final TimeOffService timeOffService) {
        this.timeOffService = timeOffService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.hexacta.hrs.services.staffing.EmployeeAssignmentService#findEndingAfter
     * (java.util.Date)
     */
    @Override
    public List<EmployeeAssignment> findEndingAfter(final Date fecha) {
        return employeeAssignmentRepository.findAssignmentsBetweenDates(fecha, null, false);
    }

    @Override
    public boolean checkAssignmentByYear(final String year) {
        return employeeAssignmentRepository.checkAssignmentByYear(year);
    }

	public static List<Status> getStatusToShow() {
		return statusToShow;
	}

	public static void setStatusToShow(List<Status> statusToShow) {
		EmployeeAssignmentServiceImpl.statusToShow = statusToShow;
	}

	@Override
	public boolean checkAssigmentIsCharged(EmployeeAssignment employeeAssignment) {
		return employeeAssignmentRepository.checkAssigmentIsCharged(employeeAssignment);
	}
	
	
    
}
